use NetAddr::IP;
use Digest::MD5 'md5_hex';
use Data::Dumper;
use Vpn;
use Interface;
use strict;

package Device;

my $sysName = "1.3.6.1.2.1.1.5.0";
my $sysDescr = "1.3.6.1.2.1.1.1.0";
my $sysLocation = "1.3.6.1.2.1.1.6.0";
my $sysObjectId = "1.3.6.1.2.1.1.2.0";
my $ifXAlias = "1.3.6.1.2.1.31.1.1.1.18";
#my $ifXName = "1.3.6.1.2.1.31.1.1.1.1";
my $ifXName = "1.3.6.1.2.1.2.2.1.2";
my $ifOperStatus = "1.3.6.1.2.1.2.2.1.8";
my $ipAdEntIfIndex = "1.3.6.1.2.1.4.20.1.2";
my $ipAdEntAddr = "1.3.6.1.2.1.4.20.1.1";
my $ipAdEntNetMask = "1.3.6.1.2.1.4.20.1.3";
my $ifPhysAddress = "1.3.6.1.2.1.2.2.1.6";
my $bgpPeerRemoteAs = "1.3.6.1.2.1.15.3.1.9";
my $bgpPeerLocalAddr = "1.3.6.1.2.1.15.3.1.5";

my $ipNetToMediaIfIndex = "1.3.6.1.2.1.4.22.1.1";
my $ipNetToMediaPhysAddress = "1.3.6.1.2.1.4.22.1.2";

my $dot1dTpFdbTable = "1.3.6.1.2.1.17.4.3";
my $dot1dTpFdbAddress = "1.3.6.1.2.1.17.4.3.1.1";
my $dot1dTpFdbPort = "1.3.6.1.2.1.17.4.3.1.2";
my $dot1qTpFdbAddress = "1.3.6.1.2.1.17.7.1.2.2.1.1";
my $dot1qTpFdbPort = "1.3.6.1.2.1.17.7.1.2.2.1.2";
my $dot1dBasePortIfIndex = "1.3.6.1.2.1.17.1.4.1.2";


sub new
{
    my ($class, $ip, $community, $oid) = @_;
    my $this = {
	ip => $ip,
	community => $community,
	sysid => $oid,
	interfaces => {},
	name => undef, 
	descr => undef, 
	location => undef,
	ipIfIndex => undef,
	vpns => {},
	defaultVPN => 0,
	id => 0,
	bgpPeers => {},
	arp => ()
    };
    bless $this, $class;
}

sub discover {
    my ($this, $mtable) = @_;
    $this->{name} = ::get($this->{ip}, $this->{community}, $sysName);
    $this->{descr} = ::get($this->{ip}, $this->{community}, $sysDescr);
    $this->{location} = ::get($this->{ip}, $this->{community}, $sysLocation);
#    $this->{id} = ::get_next($this->{ip}, $this->{community}, "$ifPhysAddress");
    
#    if(!($this->{id} =~ m/$\0x.+/)){
	$this->{id} = $this->{ip};
#    }
    $this->{id} = ::md5_hex($this->{id});

# Interfaces
    my $result = ::get_table($this->{ip}, $this->{community}, [$ifXName]);
    foreach my $key (keys(%$result)) {
	my $index = $key;
	$index =~ s/$ifXName.//;
        my $name = $result->{$key};
        my $operStatus = ::get($this->{ip}, $this->{community}, "$ifOperStatus.$index");
        if($operStatus == 1){
            my $interface = new Interface ($index, $name, $this);
	    $interface->discover;
    	    $this->{interfaces}->{$index} = $interface;
    	}
    }

# IPv4 for Interfaces
    my $ipIfIndex = ::get_table($this->{ip}, $this->{community}, [$ipAdEntIfIndex]);
    my $ips = ();
    foreach my $key (keys(%$ipIfIndex)) {
	$_ = $key;
	s/$ipAdEntIfIndex.//;
	my $ip = $_;
	my $iindex = $ipIfIndex->{$key};
	$ips->{$iindex}->{ip} = $ip;
	my $mask = ::get($this->{ip}, $this->{community}, "$ipAdEntNetMask.$ip");
	if(defined $this->{interfaces}->{$iindex}){
	    $this->{interfaces}->{$iindex}->addIp($ip, $mask);
	} else {
	    printf "[WARNING] Got IP entry %s/%s for unexistent interface index %s\n", $ip, $mask, $iindex;
	}
    }
    $this->vpn();
    
# BGP PEERS
    my $bgpPeer = ::get_table($this->{ip}, $this->{community}, [$bgpPeerRemoteAs]);
    my $bgpLocal = ::get_table($this->{ip}, $this->{community}, [$bgpPeerLocalAddr]);
    while(my ($key, $value) = each(%{$bgpPeer})){
	my $nip = $key;
	$nip =~ s/$bgpPeerRemoteAs.//;
	$this->{bgpPeers}->{$nip} = {ras => $value, local => $bgpLocal->{"$bgpPeerLocalAddr.$nip"}};
    }
#    print ::Dumper($this->{bgpPeers})
# ARP Table
    my $arpTable = ::get_table($this->{ip}, $this->{community}, [$ipNetToMediaPhysAddress]);
    while(my ($key, $value) = each(%{$arpTable})){
	my $arpIP = $key;
	$arpIP =~ s/$ipNetToMediaPhysAddress.[0-9]+.//;
	my $arpIfIndex = $key;
	$arpIfIndex =~ s/($ipNetToMediaPhysAddress).([0-9]+).+/$2/;
	push(@{$this->{arp}}, {ifindex=>$arpIfIndex, ip=>$arpIP, mac=>$value});
    }
# MAC Address table
    if($mtable){
	my $portIndex = ::get_table($this->{ip}, $this->{community}, [$dot1dBasePortIfIndex]);
	my $portIf = {};
	while(my ($key, $value) = each(%{$portIndex})){
	    my $ifindex = $key;
	    $ifindex =~ s/$dot1dBasePortIfIndex.//;
	    $portIf->{$value} = $ifindex;
	}
	print ::Dumper($portIf);
	my $macTable;
	my $portTable;

	$macTable = ::get_tnext($this->{ip}, $this->{community}, $dot1dTpFdbTable);
	$portTable = ::get_tnext($this->{ip}, $this->{community}, $dot1qTpFdbPort);
	if(defined $macTable){
	    while(my ($key, $value) = each(%{$macTable})){
		my $index = $key;
		if(!($index =~ m/$dot1dTpFdbAddress/)) { next };
		$index =~ s/$dot1dTpFdbAddress.//;
		printf "%s\n", $index;
		my $mac = $value;
		my $ifindex = $portIf->{$macTable->{"$dot1dTpFdbPort.$index"}};
		if(!defined $ifindex){
		    $ifindex = $macTable->{"$dot1dTpFdbPort.$index"};
		}
		push(@{$this->{macTable}}, {ifindex=>$ifindex, mac=>$mac});
	    }
	}

	$macTable = ::get_table($this->{ip}, $this->{community}, [$dot1qTpFdbAddress]);
	$portTable = ::get_table($this->{ip}, $this->{community}, [$dot1qTpFdbPort]);
	if(defined $macTable){
	    while(my ($key, $value) = each(%{$macTable})){
		my $index = $key;
		if(!($index =~ m/$dot1qTpFdbAddress/)) { next };
		$index =~ s/$dot1qTpFdbAddress.//;
		printf "%s\n", $index;
		my $mac = $value;
		my $ifindex = $portIf->{$macTable->{"$dot1qTpFdbPort.$index"}};
		push(@{$this->{macTable}}, {ifindex=>$ifindex, mac=>$mac});
	    }
	}
    }
}

#
# vpn discovering is specific for manufacturer,
# so making dynamic function call for it depending on sysid
#

#
# VPN for Juniper mx-40, 80 ... etc.
#
#
my $jvpnDescr = "1.3.6.1.4.1.2636.3.26.1.2.1.5";
my $jvpnIdentifier = "1.3.6.1.4.1.2636.3.26.1.2.1.7";
my $jvpnIfStatus = "1.3.6.1.4.1.2636.3.26.1.3.1.10";
my $jvpnType = "1.3.6.1.4.1.2636.3.26.1.2.1.6";
my $jloIndex = 16;

sub juniperVPN {
    my ($this) = @_;
    my $vpns = {};
    my $result = ::get_table($this->{ip}, $this->{community}, [$jvpnDescr]);
    foreach my $key (keys(%$result)) {
	my $vpnName = $result->{$key};
	my $vpnIndex = $key;
	$vpnIndex =~ s/$jvpnDescr.//;
	my $vpnType = ::get($this->{ip}, $this->{community}, "$jvpnType.$vpnIndex");
	my $vpnId = ::get($this->{ip}, $this->{community}, "$jvpnIdentifier.$vpnIndex");
	$vpns->{$vpnIndex} = new Vpn($vpnId, $vpnName, $vpnType);
    }

    my $ifs = ::get_table($this->{ip}, $this->{community}, ["$jvpnIfStatus"]);
    foreach my $ikey (keys(%$ifs)){
	my ($ifIndex, $vpnIndex);
	$ifIndex = $ikey;
	$ifIndex =~ s/(([0-9]+\.)+)([0-9]+)/$3/;
	$vpnIndex = $ikey;
	$vpnIndex =~ s/$jvpnIfStatus\.//;
	$vpnIndex =~ s/(([0-9]+\.*)+)(\.[0-9]+)/$1/;
	
	if(defined $vpns->{$vpnIndex} && defined $this->{interfaces}->{$ifIndex}){
	    $this->{interfaces}->{$ifIndex}->{vpn} = $vpns->{$vpnIndex}->{id};
	}
    }
    return $vpns;
}


my $vpnSub = {
    2636 => \&juniperVPN,
};


sub vpn {
    my ($this) = @_;
    my $oid = $this->{sysid};
    $oid =~ s/(([0-9]+\.){6})([0-9]+)(.+)/$3/;
    if(defined $vpnSub->{$oid}){
        $this->{vpns} = &{$vpnSub->{$oid}}($this);
#        print ::Dumper($this->{vpns});
        foreach my $key (keys(%{$this->{vpns}})){
    	    my $vpn = $this->{vpns}->{$key};
    	    my $vpnid = $vpn->{id};
    	    if($vpn->{type} == 10){ # l2cp vpn adjusting vpn id to format <maxneighbor><minneighbor><distinguisher>
    		my $neighbor;
		$neighbor = $vpn->{id};
		$neighbor =~ s/0x([0-9,a-f]{8})([0-9]+)/$1/;
		my $d = $vpn->{id};
		$d =~ s/0x([0-9,a-f]{8})([0-9]+)/$2/;
		my $myip = ::ip2hex($this->{interfaces}->{$jloIndex}->{ips}[1]);
		
		if($myip lt $neighbor){
		    $vpnid = $neighbor . $myip;
		} else {
		    $vpnid = $myip . $neighbor;
		}
		$vpnid .= $d;
		$vpnid = "0x" . $vpnid;
	    }
	    $this->{vpns}->{$vpnid} = $vpn;
	    delete ($this->{vpns}->{$key});
        }
    } else {
	printf "[WARNING] Cant identify vpn instances for sysid: %s (%s)\n" , $this->{sysid}, $oid;
	$this->{vpns}->{$this->{defaultVPN}} = new Vpn($this->{defaultVPN}, "Default", 0);
    }
}


sub print {
    my ($this) = @_;
    printf "-------------\nName: %s\nDescription: %s\nsysId: %s\nLocation: %s\nId: %s\n", $this->{name}, $this->{descr}, $this->{sysid}, $this->{location}, $this->{id};
#    foreach my $key (keys(%{$this->{interfaces}})) {
#	print "result: [" . ::Dumper($interface) . "]\n";
#	$this->{interfaces}->{$key}->print;
#    }
}

1;
